/**
 * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 */

package com.iwindplus.setup.server.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.github.yulichang.repository.JoinCrudRepository;
import com.iwindplus.boot.domain.constant.CommonConstant;
import com.iwindplus.boot.domain.constant.CommonConstant.ExceptionConstant;
import com.iwindplus.boot.domain.enums.BaseEnum;
import com.iwindplus.boot.domain.enums.BizCodeEnum;
import com.iwindplus.boot.domain.enums.EnableStatusEnum;
import com.iwindplus.boot.domain.enums.OssTypeEnum;
import com.iwindplus.boot.domain.enums.VodTypeEnum;
import com.iwindplus.boot.domain.exception.BizException;
import com.iwindplus.boot.domain.vo.UserBaseVO;
import com.iwindplus.boot.excel.EasyExcelUtil;
import com.iwindplus.boot.excel.listener.EasyExcelListener;
import com.iwindplus.boot.oss.service.FileService;
import com.iwindplus.boot.util.ExcelsUtil;
import com.iwindplus.mgt.domain.enums.MgtCodeEnum;
import com.iwindplus.setup.domain.dto.VodConfigDTO;
import com.iwindplus.setup.domain.dto.VodConfigEditDTO;
import com.iwindplus.setup.domain.dto.VodConfigImportDTO;
import com.iwindplus.setup.domain.dto.VodConfigSaveDTO;
import com.iwindplus.setup.domain.dto.VodConfigSearchDTO;
import com.iwindplus.setup.domain.enums.ExcelTemplateEnum;
import com.iwindplus.setup.domain.enums.SetupCodeEnum;
import com.iwindplus.setup.domain.vo.VodConfigPageVO;
import com.iwindplus.setup.domain.vo.VodConfigVO;
import com.iwindplus.setup.server.mapper.VodConfigMapper;
import com.iwindplus.setup.server.model.VodConfigDO;
import com.iwindplus.setup.server.service.VodConfigService;
import com.iwindplus.setup.server.service.handler.VodConfigImportVerifyHandler;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Validator;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.utils.StringUtil;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

/**
 * 视频点播配置业务层接口实现类.
 *
 * @author zengdegui
 * @since 2020/4/30
 */
@Slf4j
@Service
@CacheConfig(cacheNames = {"vodConfig"})
@Transactional(rollbackFor = Exception.class)
public class VodConfigServiceImpl extends JoinCrudRepository<VodConfigMapper, VodConfigDO>
    implements VodConfigService {

    @Resource
    private Validator validator;

    @Resource
    private FileService fileService;

    @CacheEvict(allEntries = true)
    @Override
    public boolean save(VodConfigSaveDTO entity) {
        this.getNameIsExist(entity.getName(), entity.getType(), entity.getOrgId());

        this.getRegionIsExist(entity.getRegion().trim(), entity.getType(), entity.getOrgId());

        entity.setStatus(EnableStatusEnum.ENABLE);
        String code = StringUtil.getUUID();
        entity.setCode(code);
        final VodConfigDO model = BeanUtil.copyProperties(entity, VodConfigDO.class);
        super.save(model);
        entity.setId(model.getId());
        return Boolean.TRUE;
    }

    @CacheEvict(allEntries = true)
    @Override
    public boolean saveOrEditBatch(List<VodConfigDTO> entityList) {
        if (CollUtil.isEmpty(entityList)) {
            return false;
        }
        List<VodConfigDTO> saveList = new ArrayList<>(10);
        List<VodConfigDTO> editList = new ArrayList<>(10);
        entityList.forEach(entity -> {
            final VodConfigDO data = super.getOne(Wrappers.lambdaQuery(VodConfigDO.class)
                .and(w -> w.eq(VodConfigDO::getName, entity.getName()).or().eq(VodConfigDO::getRegion, entity.getRegion().trim())));
            // 为空则添加
            if (Objects.isNull(data)) {
                entity.setCode(SecureUtil.md5(StringUtil.getUUID()));
                entity.setStatus(EnableStatusEnum.ENABLE);
                saveList.add(entity);
            } else {
                entity.setId(data.getId());
                editList.add(entity);
            }
        });
        if (CollUtil.isNotEmpty(saveList)) {
            List<VodConfigDO> doList = BeanUtil.copyToList(saveList, VodConfigDO.class);
            super.saveBatch(doList, DEFAULT_BATCH_SIZE);
        }
        if (CollUtil.isNotEmpty(editList)) {
            List<VodConfigDO> doList = BeanUtil.copyToList(editList, VodConfigDO.class);
            super.updateBatchById(doList, DEFAULT_BATCH_SIZE);
        }
        return Boolean.TRUE;
    }

    @CacheEvict(allEntries = true)
    @Override
    public boolean removeByIds(List<Long> ids) {
        List<VodConfigDO> list = super.listByIds(ids);
        if (CollUtil.isEmpty(list)) {
            throw new BizException(BizCodeEnum.DATA_NOT_EXIST);
        }
        super.removeByIds(ids);
        return Boolean.TRUE;
    }

    @CacheEvict(allEntries = true)
    @Override
    public boolean edit(VodConfigEditDTO entity) {
        // 编辑
        VodConfigDO data = super.getById(entity.getId());
        if (Objects.isNull(data)) {
            throw new BizException(BizCodeEnum.DATA_NOT_EXIST);
        }
        if (Boolean.TRUE.equals(data.getBuildInFlag())) {
            throw new BizException(BizCodeEnum.BUILD_IN_DATA_NOT_OPERATE);
        }
        if (CharSequenceUtil.isNotBlank(entity.getName()) && !CharSequenceUtil.equals(data.getName(), entity.getName().trim())) {
            this.getNameIsExist(entity.getName().trim(), data.getType(), entity.getOrgId());
        }
        if (CharSequenceUtil.isNotBlank(entity.getRegion()) && !CharSequenceUtil.equals(data.getRegion(), entity.getRegion().trim())) {
            this.getRegionIsExist(entity.getRegion().trim(), data.getType(), entity.getOrgId());
        }
        if (CharSequenceUtil.isNotBlank(entity.getCode()) && !CharSequenceUtil.equals(data.getCode(), entity.getCode().trim())) {
            this.getCodeIsExist(entity.getCode().trim());
        }
        if (Objects.isNull(entity.getVersion())) {
            entity.setVersion(data.getVersion());
        }
        final VodConfigDO model = BeanUtil.copyProperties(entity, VodConfigDO.class);
        super.updateById(model);
        return Boolean.TRUE;
    }

    @CacheEvict(allEntries = true)
    @Override
    public boolean editStatus(Long id, EnableStatusEnum status) {
        VodConfigDO data = super.getById(id);
        if (Objects.isNull(data)) {
            throw new BizException(BizCodeEnum.DATA_NOT_EXIST);
        }
        if (Boolean.TRUE.equals(data.getBuildInFlag())) {
            throw new BizException(BizCodeEnum.BUILD_IN_DATA_NOT_OPERATE);
        }
        if (status.equals(data.getStatus())) {
            throw new BizException(BizCodeEnum.ALREADY_OPERATED);
        }
        VodConfigDO entity = new VodConfigDO();
        entity.setId(id);
        entity.setStatus(status);
        entity.setVersion(data.getVersion());
        super.updateById(entity);
        return Boolean.TRUE;
    }

    @CacheEvict(allEntries = true)
    @Override
    public boolean editBuildIn(Long id, Boolean buildInFlag) {
        VodConfigDO data = super.getById(id);
        if (Objects.isNull(data)) {
            throw new BizException(BizCodeEnum.DATA_NOT_EXIST);
        }
        if (buildInFlag.equals(data.getBuildInFlag())) {
            throw new BizException(BizCodeEnum.ALREADY_OPERATED);
        }
        VodConfigDO param = new VodConfigDO();
        param.setId(id);
        param.setBuildInFlag(buildInFlag);
        param.setVersion(data.getVersion());
        super.updateById(param);
        return Boolean.TRUE;
    }

    @Override
    public IPage<VodConfigPageVO> page(PageDTO<VodConfigDO> page, VodConfigSearchDTO entity) {
        page.setOptimizeCountSql(Boolean.FALSE);
        page.setOptimizeJoinOfCountSql(Boolean.FALSE);
        final LambdaQueryWrapper<VodConfigDO> queryWrapper = Wrappers.lambdaQuery(VodConfigDO.class);
        if (Objects.nonNull(entity.getStatus())) {
            queryWrapper.eq(VodConfigDO::getStatus, entity.getStatus());
        }
        if (CharSequenceUtil.isNotBlank(entity.getName())) {
            queryWrapper.eq(VodConfigDO::getName, entity.getName().trim());
        }
        if (CharSequenceUtil.isNotBlank(entity.getCode())) {
            queryWrapper.eq(VodConfigDO::getCode, entity.getCode().trim());
        }
        if (Objects.nonNull(entity.getType())) {
            queryWrapper.eq(VodConfigDO::getType, entity.getType());
        }
        if (CharSequenceUtil.isNotBlank(entity.getRegion())) {
            queryWrapper.eq(VodConfigDO::getRegion, entity.getRegion().trim());
        }
        if (CharSequenceUtil.isNotBlank(entity.getAccessKey())) {
            queryWrapper.eq(VodConfigDO::getAccessKey, entity.getAccessKey().trim());
        }
        if (Objects.nonNull(entity.getOrgId())) {
            queryWrapper.eq(VodConfigDO::getOrgId, entity.getOrgId());
        }
        // 排序
        List<OrderItem> orders = page.getOrders();
        if (CollUtil.isEmpty(orders)) {
            orders = new ArrayList<>(10);
            OrderItem item = OrderItem.desc(CommonConstant.DbConstant.MODIFIED_TIME);
            orders.add(item);
        }
        orders.forEach(order -> {
            String column = order.getColumn();
            String underline = CharSequenceUtil.toUnderlineCase(column);
            order.setColumn(underline);
        });
        page.setOrders(orders);
        final PageDTO<VodConfigDO> modelPage = super.page(page, queryWrapper);
        return modelPage.convert(model -> BeanUtil.copyProperties(model, VodConfigPageVO.class));
    }

    @Cacheable(key = "#root.methodName + '_' + #p0", condition = "#p0 != null", unless = "#result == null")
    @Override
    public VodConfigVO getByCode(String code) {
        VodConfigDO data = super.getOne(Wrappers.lambdaQuery(VodConfigDO.class)
            .eq(VodConfigDO::getCode, code.trim()));
        if (Objects.isNull(data)) {
            throw new BizException(SetupCodeEnum.VOD_CONFIG_NOT_EXIST);
        }
        if (EnableStatusEnum.DISABLE == data.getStatus()) {
            throw new BizException(SetupCodeEnum.VOD_CONFIG_DISABLED);
        } else if (EnableStatusEnum.LOCKED == data.getStatus()) {
            throw new BizException(SetupCodeEnum.VOD_CONFIG_LOCKED);
        }
        VodConfigVO result = BeanUtil.copyProperties(data, VodConfigVO.class);
        return result;
    }

    @Cacheable(key = "#root.methodName + '_' + #p0", condition = "#p0 != null", unless = "#result == null")
    @Override
    public VodConfigVO getDetail(Long id) {
        VodConfigDO data = super.getById(id);
        if (Objects.isNull(data)) {
            throw new BizException(BizCodeEnum.DATA_NOT_EXIST);
        }
        VodConfigVO result = BeanUtil.copyProperties(data, VodConfigVO.class);
        return result;
    }

    @Override
    public void exportTemplate(HttpServletResponse response) {
        final String fileName = new StringBuilder(FileUtil.getPrefix(ExcelTemplateEnum.VOD_CONFIG_TEMPLATE.getDesc()))
            .append(CommonConstant.SymbolConstant.POINT)
            .append(FileUtil.getSuffix(ExcelTemplateEnum.VOD_CONFIG_TEMPLATE.getValue())).toString();
        try {
            Workbook workbook = this.getWorkbook();
            ExcelsUtil.downloadFile(workbook, fileName, response);
        } catch (IOException ex) {
            log.error(ExceptionConstant.IO_EXCEPTION, ex);
        }
    }

    @CacheEvict(allEntries = true)
    @Override
    public void importByTemplate(MultipartFile file, UserBaseVO userInfo, HttpServletResponse response) {
        EasyExcelListener<VodConfigImportDTO> importResult;
        try {
            importResult = EasyExcelUtil.importExcel(file.getInputStream(), this.validator, null, VodConfigImportDTO.class,
                new VodConfigImportVerifyHandler(this, userInfo), 2);
        } catch (IOException ex) {
            log.error(ExceptionConstant.EXCEPTION, ex);

            throw new BizException(BizCodeEnum.EXCEL_IMPORT_ERROR);
        }
        List<VodConfigImportDTO> failList = importResult.getFailList();
        this.checkExcelData(importResult.getList());
        // 校验数据是否合规
        if (CollUtil.isNotEmpty(failList)) {
            final String sourceFileName = file.getOriginalFilename();
            String fileName = ExcelsUtil.getExcelErrorFile(sourceFileName);
            EasyExcelUtil.exportExcel(response, failList, VodConfigImportDTO.class, fileName, null);
        } else {
            // 正确的数据处理
            this.saveRightData(importResult.getRightList(), userInfo);
        }
    }

    private Workbook getWorkbook() throws IOException {
        String templateUrl = ExcelTemplateEnum.VOD_CONFIG_TEMPLATE.getValue();
        try (InputStream inputStream = this.fileService.getResource(templateUrl).getInputStream()) {
            final Workbook workbook = WorkbookFactory.create(inputStream);
            final List<String> vodTypeList = Arrays.stream(OssTypeEnum.values()).map(m -> m.getDesc())
                .collect(Collectors.toCollection(ArrayList::new));
            String[] templates = vodTypeList.toArray(new String[vodTypeList.size()]);
            ExcelsUtil.dropDownOption(workbook, Arrays.asList(templates), 'B', 2, null);
            return workbook;
        }
    }

    private void saveRightData(List<VodConfigImportDTO> rightList, UserBaseVO userInfo) {
        List<VodConfigDO> entities = new ArrayList<>(10);
        rightList.stream().distinct().forEach(excelData -> {
            VodConfigDO entity = VodConfigDO.builder()
                .name(Optional.ofNullable(excelData.getName()).map(String::trim).orElse(null))
                .type(CharSequenceUtil.isNotBlank(excelData.getType()) ? BaseEnum.fromDesc(excelData.getType(), VodTypeEnum.class) : null)
                .region(Optional.ofNullable(excelData.getRegion()).map(String::trim).orElse(null))
                .accessKey(Optional.ofNullable(excelData.getAccessKey()).map(String::trim).orElse(null))
                .secretKey(Optional.ofNullable(excelData.getSecretKey()).map(String::trim).orElse(null))
                .stsEndpoint(Optional.ofNullable(excelData.getStsEndpoint()).map(String::trim).orElse(null))
                .roleArn(Optional.ofNullable(excelData.getRoleArn()).map(String::trim).orElse(null))
                .notifyUrl(Optional.ofNullable(excelData.getNotifyUrl()).map(String::trim).orElse(null))
                .remark(Optional.ofNullable(excelData.getRemark()).map(String::trim).orElse(null))
                .orgId(userInfo.getOrgId())
                .build();
            entities.add(entity);
        });
        if (CollUtil.isNotEmpty(entities)) {
            List<VodConfigDTO> dtoList = BeanUtil.copyToList(entities, VodConfigDTO.class);
            this.saveOrEditBatch(dtoList);
        }
    }

    private void checkExcelData(List<VodConfigImportDTO> list) {
        // 校验表格数据是否为空
        if (CollUtil.isEmpty(list)) {
            throw new BizException(BizCodeEnum.EXCEL_DATA_EMPTY);
        }
        int totalRow = list.size();
        // 校验表格数据行数是不是过大
        if (totalRow > CommonConstant.ExcelConstant.EXCEL_MAX_ROW) {
            throw new BizException(BizCodeEnum.EXCEL_ROW_TOO_BIG);
        }
    }

    private void getNameIsExist(String name, VodTypeEnum type, Long orgId) {
        boolean result = SqlHelper.retBool(super.count(Wrappers.lambdaQuery(VodConfigDO.class)
            .eq(VodConfigDO::getType, type)
            .eq(VodConfigDO::getName, name.trim())
            .eq(VodConfigDO::getOrgId, orgId)));
        if (Boolean.TRUE.equals(result)) {
            throw new BizException(SetupCodeEnum.REGION_EXIST);
        }
    }

    private void getRegionIsExist(String region, VodTypeEnum type, Long orgId) {
        boolean result = SqlHelper.retBool(super.count(Wrappers.lambdaQuery(VodConfigDO.class)
            .eq(VodConfigDO::getType, type)
            .eq(VodConfigDO::getAccessKey, region.trim())
            .eq(VodConfigDO::getOrgId, orgId)));
        if (Boolean.TRUE.equals(result)) {
            throw new BizException(SetupCodeEnum.REGION_EXIST);
        }
    }

    private void getCodeIsExist(String code) {
        boolean result = SqlHelper.retBool(super.count(Wrappers.lambdaQuery(VodConfigDO.class)
            .eq(VodConfigDO::getCode, code)));
        if (Boolean.TRUE.equals(result)) {
            throw new BizException(MgtCodeEnum.CODE_EXIST);
        }
    }
}
